import { ClampToEdgeWrapping, Color, DepthFormat, DepthTexture, Euler, Frustum, LinearFilter, Material, MathUtils, Matrix4, Mesh, NearestFilter, NearestMipmapNearestFilter, Object3D, PerspectiveCamera, Quaternion, RepeatWrapping, SRGBColorSpace, Texture, UnsignedShortType, Vector3, VideoTexture, WebGLRenderTarget, WebGLRenderer } from "three";
import { App, Point, Polyline } from "../../Engine";
import Helper from "../../utils/Helper";
import { TransformControls } from "three/examples/jsm/controls/TransformControls";
import FrustumLineFrame from "./FrustumLineFrame";
import Events from "../../application/constants/Events";
import MeshUtil from "../../renderExtension/utils/MeshUtil";
import videojs from "video.js";
import DepthTargetHelper from "../../utils/DepthTargetHelper";
import VideoUtil from "./utils/VideoUtil";

/**
 * 视频投影对象
 */
export default class ProjectTextureObject {
    private app: App;
    private virturalCamera: PerspectiveCamera;
    public static projectObjectList: Array<ProjectTextureObject> = new Array<ProjectTextureObject>();
    private static alphaTexture: Texture;
    private frustumLineObject: FrustumLineFrame;
    private isOpenProject: boolean = false;
    private transformControls: TransformControls;
    private transformControls2: TransformControls;
    private textureProjectData: { isOpenTextureProject: boolean, isCloned: boolean, virturalProjectMatrix: Matrix4, virturalViewMatrix: Matrix4, projectTexture: Texture, depthTexture: Texture, textureProjectMix: number, isCoverShadow: boolean };
    private distortionData: { isOpenDistortion: boolean, distortionStep: number, distortionAngle: number };
    private maskData: { isOpenProjectMask: boolean, projectMaskTexture: Texture } = { isOpenProjectMask: false, projectMaskTexture: null };;
    private depthTargetHelper: DepthTargetHelper;
    private clonedMeshArray: Array<Mesh>;
    private refreshEvent;
    private controlModel: 'translate' | 'rotate' = 'translate';
    private isProjectToScene = true; //是否投影到整个场景
    private objsProjected: Array<Object3D> = new Array<Object3D>();
    private static map: Map<string, Texture> = new Map<string, Texture>();

    /**
     * 纹理投影
     * @example let aspect = 1.2722371967654986;
            let cameraParam = {
                cameraPosition: new GE.Point(13377542.119303564, 3540032.0606621173, 500, '3857'),
                targetPosition: new GE.Point(13377542.119303564, 3540532.0606621173, 0, '3857'),
                fov: 10,
                aspect: aspect,
                near: 10,
                far: 100000,
                controlModel: 'translate'
            }

            let projectTextureObject = new GE.ProjectTextureObject();
            projectTextureObject.setCamera(cameraParam); //设置相机

            let url = 'http://wlres.dev.gisway.cn:9876/resources/video/test.mp4';
            let textureProjectMix = 1;
            let videoOptions = { texture: url, isVideoTexture: true, textureProjectMix: textureProjectMix };
            projectTextureObject.setVideo(videoOptions); //设置视频的相关参数
            projectTextureObject.openProject(); //打开投影
            //projectTextureObject.closeAnalysis(); //关闭投影
     */
    public constructor() {
        this.app = App.getInstance();
        this.clonedMeshArray = new Array<Mesh>();
        if (!ProjectTextureObject.alphaTexture) {
            ProjectTextureObject.alphaTexture = MeshUtil.createAlphaTexture();
        }

        this.depthTargetHelper = new DepthTargetHelper();
        this.depthTargetHelper.initRenderTarget();
    }

    /**
     * 设置投影需要的video
     * @param videoOptions video数据
     * @param videoOptions.texture 投影数据的url
     * @param videoOptions.isVideoTexture 是否是video数据。true表示视频数据，false表示图片数据。默认为false
     */
    public setVideo(videoOptions: { texture, isVideoTexture?: boolean }) {
        let videoUrl = videoOptions.texture;
        let curVideoTexture = ProjectTextureObject.map.get(videoUrl);
        if(!curVideoTexture) {
            if (!(videoOptions.texture instanceof Texture)) {
                if (!videoOptions.isVideoTexture) {
                    videoOptions.texture = Helper.createTexture(videoOptions.texture as string);
                }
                else {
                     let videoUtil = new VideoUtil();
                    let videoElement = videoUtil.makeVideoElement(videoUrl);

                    let texture = new VideoTexture(videoElement);
                    texture.minFilter = LinearFilter;
                    texture.magFilter = LinearFilter;
                    texture.wrapS = RepeatWrapping;
                    texture.wrapT = RepeatWrapping;
                    texture.colorSpace = SRGBColorSpace;
    
                    videoOptions.texture = texture;
                }
            }

            ProjectTextureObject.map.set(videoUrl, videoOptions.texture)
        }else{
            videoOptions.texture = ProjectTextureObject.map.get(videoUrl) as Texture;
        }

 

        this.textureProjectData = { isOpenTextureProject: false, isCloned: false, virturalProjectMatrix: new Matrix4(), virturalViewMatrix: new Matrix4(),
             projectTexture: videoOptions.texture, depthTexture: new Texture(), textureProjectMix: 1.0, isCoverShadow: true };
    }
   
    private updateSceneMaterial(textureProjectData: { isOpenTextureProject: boolean, isCloned: boolean, virturalProjectMatrix: Matrix4, virturalViewMatrix: Matrix4, projectTexture: Texture, depthTexture: Texture, textureProjectMix: number }) {
        let self = this;
        let curFrustum: Frustum;
        let virturalFrustumList = new Array<Frustum>();
        ProjectTextureObject.projectObjectList.forEach(projectObject => {
            let virturalFrustum = new Frustum();
            let camera = projectObject.virturalCamera;
            virturalFrustum.setFromProjectionMatrix(camera.projectionMatrix.clone().multiply(camera.matrixWorldInverse));
            virturalFrustumList.push(virturalFrustum);
            virturalFrustum['projectIndex'] = projectObject['projectIndex'];

            if (projectObject == self) {
                curFrustum = virturalFrustum;
            }
        });

        if (!curFrustum) {
            return;
        }

        let oldClonedArray = new Array<Mesh>();
        this.clonedMeshArray.forEach(element => {
            oldClonedArray.push(element);
        });
        this.clonedMeshArray.splice(0, this.clonedMeshArray.length);

        if(!this.isProjectToScene && this.objsProjected.length > 0){ //投影到特定的物体上
            this.objsProjected.forEach(element => {
                if(element instanceof Mesh){
                    self.executeProject(element, curFrustum, virturalFrustumList, textureProjectData);
                }
                Helper.traverseObject(element, function (obj) {
                    return self.executeProject(obj, curFrustum, virturalFrustumList, textureProjectData);
                });
            })
        }else if(this.isProjectToScene){ //投影到整个场景上
            Helper.traverseObject(this.app.three.scene, function (obj) {
                return self.executeProject(obj, curFrustum, virturalFrustumList, textureProjectData);
            });
        }
       
        oldClonedArray.forEach((element: Mesh) => {
            if (element.parent) {
                element.parent.remove(element);
            }
            MeshUtil.dispose(element);
        });
    }

    private executeProject(obj, curFrustum, virturalFrustumList, textureProjectData){
        if (obj['isCloned']) { //这是被克隆的对象，用于解决一个mesh被多个视频投影的问题
            return false;
        }

        if (obj instanceof TransformControls) { //控制器中的mesh不参与投影
            return false
        }

        let self = this;

        try {
            if (obj.type == 'Mesh') {
                // obj.geometry.computeBoundingSphere();
                if (!(obj as Mesh).geometry.boundingSphere) {
                    (obj as Mesh).geometry.computeBoundingSphere();
                }
                const boundingSphere = (obj as Mesh).geometry.boundingSphere.clone();
                obj.updateMatrixWorld(); // 确保世界矩阵更新
                boundingSphere.applyMatrix4(obj.matrixWorld); // 将包围球变换到世界空间

                if (curFrustum.intersectsSphere(boundingSphere)) { //该mesh被当前的视椎体包含
                    let isContainedByOther = false;
                    label:
                    //检查该mesh是否被其他的视椎体包含
                    for (let i = 0; i < virturalFrustumList.length; i++) {
                        let frustum = virturalFrustumList[i];
                        if (frustum == curFrustum) {
                            continue;
                        }

                        if (frustum.intersectsSphere(boundingSphere)) { //被其他视椎体包含
                            let clonedGeo = (obj as Mesh).geometry.clone();
                            let clonedMaterial;
                            if (obj.material.length > 1) {
                                clonedMaterial = [];
                                obj.material.forEach(element => {
                                    let copyMaterial = (element as Material).clone();
                                    copyMaterial['map'] = ProjectTextureObject.alphaTexture;
                                    copyMaterial.transparent = true;
                                    copyMaterial.opacity = 0;
                                    // copyMaterial.depthTest = false;
                                    clonedMaterial.push(copyMaterial);
                                });
                            } else {
                                clonedMaterial = (obj.material as Material).clone();
                                clonedMaterial.transparent = true;
                                clonedMaterial.map = ProjectTextureObject.alphaTexture;
                                clonedMaterial.opacity = 0;
                                // clonedMaterial.depthTest = false;
                            }

                            let cloneMesh = new Mesh(clonedGeo, clonedMaterial);
                            cloneMesh.position.copy(obj.position);
                            cloneMesh.rotation.copy(obj.rotation);
                            cloneMesh.scale.copy(obj.scale);
                            cloneMesh.renderOrder = obj.renderOrder + 1;
                            cloneMesh['isCloned'] = true;
                            obj.parent.add(cloneMesh);
                            self.clonedMeshArray.push(cloneMesh);

                            textureProjectData.isCloned = true;
                            self.setMaterial(cloneMesh, textureProjectData, self.distortionData, self.maskData);
                            isContainedByOther = true;
                            break label;
                        }
                    }

                    if (!isContainedByOther) { //只被当前的视椎体包含
                        textureProjectData.isCloned = false;
                        self.setMaterial(obj, textureProjectData, self.distortionData, self.maskData);
                    }
                }

                // return true
            }
        } catch (error) {
            console.log(error);
        }

        return true;
    }

    private setMaterial(obj, textureProjectData, distortionData?, maskData?) {
        if (obj.material.length > 1) {
            obj.material.forEach(element => {
                if (element.material.setTextrueProject) {
                    element.material.setTextrueProject(textureProjectData);
                    if (distortionData) {
                        element.material.setProjectDistortion(distortionData);
                    }
                    if (maskData) {
                        element.material.setProjectMask(maskData);
                    }
                    element.material.needsUpdate = true;
                }
            });
        } else {
            if (obj.material.setTextrueProject) {
                obj.material.setTextrueProject(textureProjectData);
                if (distortionData) {
                    obj.material.setProjectDistortion(distortionData);
                }
                if (maskData) {
                    obj.material.setProjectMask(maskData);
                }
                obj.material.needsUpdate = true;
            }
        }
    }

    /**
     * 反畸变
     * @param distortionData 反畸变的数据
     * @param distortionData.isOpenDistortion 是否开启反畸变
     * @param distortionData.distortionStep 步长
     * @param distortionData.distortionAngle 反畸变的角度
     */
    public setDistortion(distortionData: { isOpenDistortion: boolean, distortionStep: number, distortionAngle: number }) {
        this.distortionData = distortionData;

        if (this.isOpenProject) {
            this.updateSceneMaterial(this.textureProjectData);
        }
    }

    /**
     * 设置投影的遮罩图片，图片的透明部分被投影，非透明部分不被投影
     * @param maskData 遮罩的数据
     * @param maskData.isOpenProjectMask 是否开启遮罩
     * @param maskData.maskImage 遮罩图片的url
     */
    public setMask(maskData: { isOpenProjectMask: boolean, maskImage: string }) {
        this.maskData.isOpenProjectMask = maskData.isOpenProjectMask;

        if (!this.maskData.isOpenProjectMask && this.maskData.projectMaskTexture) {
            this.maskData.projectMaskTexture.dispose();
            this.maskData.projectMaskTexture = null;

            this.updateSceneMaterial(this.textureProjectData);
        } else {
            let self = this;
            this.maskData.projectMaskTexture = Helper.createTexture(maskData.maskImage, function (isSuccess, texture) {
                if (isSuccess) {
                    if (self.isOpenProject) {
                        self.maskData.projectMaskTexture = texture;
                        self.updateSceneMaterial(self.textureProjectData);
                    }
                }
            });
        }
    }

    /**
     * 设置投影的混合度
     * @param mix 投影的混合度
     */
    public setProjectMix(mix: number) {
        this.textureProjectData.textureProjectMix = mix;
        if (this.isOpenProject) {
            this.updateSceneMaterial(this.textureProjectData);
        }
    }

    /**
     * 相对于投影相机被遮挡的部分，是否投影
     * @param isCoverShadow 被遮挡的部分是否投影，默认值是true
     */
    public setCoverShadow(isCoverShadow: boolean) {
        this.textureProjectData.isCoverShadow = isCoverShadow;
        if (this.isOpenProject) {
            this.updateSceneMaterial(this.textureProjectData);
        }
    }

    private updateVirturalCameraMatrix() {
        this.virturalCamera.updateMatrix();
        this.virturalCamera.updateWorldMatrix(true, true);
        this.virturalCamera.updateMatrixWorld(true);
        this.virturalCamera.updateProjectionMatrix();
    }

    private vituralCameraData;
    /**
     * 为投影相机设置参数
     * @param cameraParam 投影相机需要的参数
     * @param cameraParam.cameraPosition 投影相机的位置
     * @param cameraParam.targetPosition 投影相机看向的位置
     * @param cameraParam.fov 投影相机的可视角度
     * @param cameraParam.aspect 投影的长宽比例
     * @param cameraParam.near 投影相机的近平面
     * @param cameraParam.far 投影相机的远平面
     * @param cameraParam.controlModel 控制投影相机的方式，translate表示平移，rotate表示旋转
     */
    public setCamera(cameraParam: { cameraPosition: Point, targetPosition: Point, fov: number, aspect: number, near: number, far: number, controlModel: 'translate' | 'rotate' }) {
        this.vituralCameraData = cameraParam;

        cameraParam.cameraPosition.toEPSGWeb();
        this.virturalCamera = new PerspectiveCamera(cameraParam.fov, cameraParam.aspect, cameraParam.near, cameraParam.far);
        this.virturalCamera.position.copy(cameraParam.cameraPosition);
        this['projectIndex'] = ProjectTextureObject.projectObjectList.length;
        ProjectTextureObject.projectObjectList.push(this);

        cameraParam.targetPosition.toEPSGWeb();

        this.virturalCamera.up.set(0, 0, 1);
        this.virturalCamera.lookAt(cameraParam.targetPosition);
        this.app.add(this.virturalCamera);

        if (cameraParam.controlModel) {
            this.setControlModel(cameraParam.controlModel);
        }
    }

    /**
     * 获得投影相机的数据
     */
    public getVirturalCameraData() {
        return this.vituralCameraData;
    }

    /**
     * 打开投影功能
     * @param cameraParam 投影需要用到是相机参数
     * @param cameraParam.cameraPosition 投影相机的位置
     * @param cameraParam.targetPosition 投影相机看向的位置
     * @param cameraParam.fov 投影相机的可视角度
     * @param cameraParam.aspect 投影的长宽比例
     * @param cameraParam.near 投影相机的近平面
     * @param cameraParam.far 投影相机的远平面
     * @param cameraParam.color 投影可视区域线框的颜色
     */
    public openProject() {
        if (this.isOpenProject) {
            return;
        }
        this.isOpenProject = true;

        this.updateVirturalCameraMatrix();
        this.depthTargetHelper.preRenderTarget(this.virturalCamera);

        this.textureProjectData.isOpenTextureProject = true;
        this.textureProjectData.virturalProjectMatrix = this.virturalCamera.projectionMatrix.clone(); //.multiply(this.virturalCamera.matrixWorldInverse);
        this.textureProjectData.virturalViewMatrix = this.virturalCamera.matrixWorld.clone();
        this.textureProjectData.depthTexture = this.depthTargetHelper.renderTarget.depthTexture;

        this.updateSceneMaterial(this.textureProjectData);

        this.refreshEvent = Helper.debounce2(this.refreshLisrener.bind(this), 5, false);
        this.app.on(Events.AddTile, this.refreshEvent);
    }

    /**
     * 关闭投影
     */
    public closeProject() {
        this.hideControl();

        this.textureProjectData.isOpenTextureProject = false;
        if(this.distortionData){
            this.distortionData.isOpenDistortion = false;
        }
        if(this.maskData){
            this.maskData.isOpenProjectMask = false;
        }
        this.updateSceneMaterial(this.textureProjectData);

        let index = ProjectTextureObject.projectObjectList.indexOf(this);
        ProjectTextureObject.projectObjectList.splice(index, 1);
        this.app.remove(this.virturalCamera);

        this.app.remove(this.frustumLineObject);
        MeshUtil.dispose(this.frustumLineObject);
        this.frustumLineObject = null;

        this.app.off(Events.AddTile, this.refreshEvent);

        this.isOpenProject = false;
    }

    /**
     * 打开投影控制器，并且设置控制器的模式
     * @param model 控制器的模式：'translate'表示移动模式; 'rotate'表示旋转模式
     */
    public setControlModel(model: 'translate' | 'rotate') {
        if (model != 'translate' && model != 'rotate') {
            return;
        }

        if (!this.transformControls) {
            this.transformControls = new TransformControls(this.app.three.camera, this.app.three.renderer.domElement);
            this.setTransformControl(this.transformControls);
            // this.app.add(this.transformControls);
			this.app.add( this.transformControls.getHelper() );

            this.transformControls2 = new TransformControls(this.app.three.camera, this.app.three.renderer.domElement);
            // this.setTransformControl(this.transformControls2);
            // this.app.add(this.transformControls2);
			this.app.add( this.transformControls2.getHelper() );
        }

        if (!this.frustumLineObject) {
            this.frustumLineObject = new FrustumLineFrame(this.virturalCamera);
            // this.virturalCamera.add(this.lineObject);
            // this.lineObject.position.copy(this.virturalCamera.position);
            this.app.add(this.frustumLineObject);
        }

        this.transformControls.attach(this.virturalCamera);
        this.transformControls.setMode(model);

        this.transformControls2.attach(this.frustumLineObject);
        this.transformControls2.setMode(model);

        this.controlModel = model;
    }

    /**
     * 关闭投影控制器
     */
    public hideControl() {
        if (!this.transformControls) {
            return;
        }

        this.app.remove(this.transformControls.getHelper());
        this.transformControls.detach();
        this.transformControls.dispose();
        this.transformControls = null;

        this.app.remove(this.transformControls2.getHelper());
        this.transformControls2.detach();
        this.transformControls2.dispose();
        this.transformControls2 = null;

        this.app.remove(this.frustumLineObject);
        MeshUtil.dispose(this.frustumLineObject);
        this.frustumLineObject = null;
    }

    private setTransformControl(transformControls) {
        transformControls.enabled = true;
        Helper.traverseObject(transformControls, function (element) {
            if (element instanceof Mesh || element.type == 'Mesh') {
                element.material.clippingPlanes = null;
            }
        });
        let self = this;
        // transformControls.addEventListener('rotationAngle-changed', function (event) {
        //     // let modelMatrix: Matrix4 = event.target.object.matrixWorld;

        //     self.updateVirturalCameraMatrix();

        //     self.textureProjectData.virturalProjectMatrix = self.virturalCamera.projectionMatrix.clone();//.multiply(self.virturalCamera.matrixWorldInverse);
        //     self.textureProjectData.virturalViewMatrix = self.virturalCamera.matrixWorld.clone();
        //     self.updateSceneMaterial(self.textureProjectData);
        // });
        transformControls.addEventListener('dragging-changed', function (event) {
            self.app.camera.orbit.enabled = !event.value;
        });
        transformControls.addEventListener('change', function (event) {
            if (!event.target.object) {
                return;
            }

            self.refreshLisrener();
        });
    }

    private refreshLisrener() {
        if(!this.virturalCamera){
            return;
        }
        this.updateVirturalCameraMatrix();
        this.depthTargetHelper.preRenderTarget(this.virturalCamera);

        if(!this.textureProjectData){
            return;
        }
        this.textureProjectData.virturalProjectMatrix = this.virturalCamera.projectionMatrix.clone();//.multiply(self.virturalCamera.matrixWorldInverse);
        this.textureProjectData.virturalViewMatrix = this.virturalCamera.matrixWorld.clone();
        this.textureProjectData.depthTexture = this.depthTargetHelper.renderTarget.depthTexture;
        this.textureProjectData.depthTexture.needsUpdate = true;

        this.updateSceneMaterial(this.textureProjectData);
    }

    /**
     * 设置投影相机绕轴旋转
     * @param axis 投影相机的旋转轴
     * @param angle 投影相机的旋转角度
     */
    public rotateByAxis(axis: {x: number, y: number, z: number}, angle: number) {
        if (!this.virturalCamera) {
            return;
        }
        let radians = (Math.PI * angle) / 180;
        this.virturalCamera.rotateOnAxis(new Vector3(axis.x, axis.y, axis.z), radians);
        this.hideControl();
        this.setControlModel(this.controlModel);
    }

    /**
     * 用四元数设置投影相机的旋转
     * @param rotation 用于旋转相机的四元数
     */
    public rotateByQuaternion(rotation: Quaternion){
        if (!this.virturalCamera) {
            return;
        }
        this.virturalCamera.setRotationFromQuaternion(rotation);
        this.hideControl();
        this.setControlModel(this.controlModel);
    }

    /**
     * 设置投影相机的位置
     * @param cameraPosition 投影相机的位置
     */
    public setPosition(cameraPosition: Point){
        if (!this.virturalCamera) {
            return;
        }
        cameraPosition.toEPSGWeb();
        this.virturalCamera.position.set(cameraPosition.x, cameraPosition.y, cameraPosition.z);
        this.hideControl();
        this.setControlModel(this.controlModel);
    }

    /**
     * 设置投影相机的fov
     * @param fov 相机的fov
     */
    public setFov(fov: number){
        if (!this.virturalCamera) {
            return;
        }
        this.virturalCamera.fov = fov;
        this.hideControl();
        this.setControlModel(this.controlModel);
    }
    
    /**
     * 是否投影到整个场景。如果不指定，则投影到整个场景。如果指定，则只投影指定的物体，投影到指定的物体需要通过addObjProjected方法指定
     * @param isProjectToScene 
     */
    public setProjectToScene(isProjectToScene: boolean){
        this.isProjectToScene = isProjectToScene;
        this.refreshLisrener();
    }

    /**
     * 特定的物体接受投影。如果不指定，则所有的物体接受投影。该方法只有在调用setProjectToScene(false)时才有效
     * @param obj 
     */
    public addObjProjected(obj: Object3D){
        this.objsProjected.push(obj);
        this.refreshLisrener();
    }

    /**
     * 把之前指定的接受投影的物体移除掉，不接受投影。该方法只有在调用setProjectToScene(false)时才有效
     * @param obj 
     */
    public removeObjProjected(obj: Object3D){
        let index = this.objsProjected.indexOf(obj);
        if (index !== -1) {
            this.objsProjected.splice(index, 1);
            this.refreshLisrener();
        }
    }
}